#ifndef _FORK_BOX_H_JM_20110830_
#define _FORK_BOX_H_JM_20110830_
/** @file ForkBox.h
 * _OK_ Rozdelenie vypoctu do viacerych vetiev spojene s delenim dat.
 **/

// ##
#include "RootBox.h"
#include "../common_bbx/timer/Timer.h"
#include "../common_bbx/sharedBoxData/HashTableHolder.h"
//#include "input/HardCodedDataInputBox.h"
// ## C++
#include <vector>
// ## bobox
#include <bobox_box.hpp>
#include <bobox_types.hpp>


namespace jm4bbx {
	/**
	 * ForkBox zabezpecuje rovetvenie vypoctu s delenim dat v jednom stlpci.
	 * 
	 * ForkBox ockava na vstupe ({@link INPUT_GROUP}) data, ktore rovnomerne
	 * rozdeli do novovzniknutych vetiev vypoctu. Pocet stlpcov a typy 
	 * dat v stlpcoch nie su nijak obmedzene, perotze box na ne vobec 
	 * nesiaha len zmeni nastavenia rozsahov.
	 * 
	 * Pocet vetiev do ktorych box rozdeli vypocet je urceny pomocou 
	 * parameteru boxu z modelu s menom {@link FORK_FACTOR_PARAM_NAME}. 
	 * Jedna sa o defakto povinny parameter. Pri vynechani tohoto parametru 
	 * z definicie  modelu sa pouzije hodnota 1, co znamena ze neprebehne ziadne 
	 * vetvenie. Hodnota vetvenia je ulozena v membre {@link forkFactor_}.
	 * 
	 * Rozdelenie dat je co narovnomernejsie medzi vsetky vytvarane vetvy.
	 * Pokial je pocet dat n a pocet vetiev m a zvysok po deleni r. Ak je 
	 * r == 0 tak velkosti jednotlivych usekov su rovnomerne a to velkosti n/m.
	 * Pokial vsak r != 0 tak prvych r usekov ma velkost n/m + 1 a zvysnych 
	 * m - r usekov ma velkost n/m. Tymto sposobom maju vsetky vetvy vypoctu 
	 * v podstate rovnaky usek dat pre spracovanie.
	 * 
	 * ForkBox naviac umoznuje vyuzit cakanie na vstupne signaly ako rozsirenie 
	 * moznosti cakania na vypocet a tiez umoznuje informovanie vystpnymi 
	 * signalami. Signaly su realizovane v sulade  so signalmi z {@link RootBox}. 
	 * 
	 **/
	class ForkBox : public RootBox {
		JM_USE_LOGGER(ForkB)
	public:
		/// Vstupna skupina pre data.
		static const groupType INPUT_GROUP;
		/// Pociatocna skupina pre vetvenie vypoctu.
		/// Ostatne vystupne skupiny su na poziciach s posunom 
		/// +1, +2 ... ,+{@link forkFactor_} -1
		static const groupType OUTPUT_GROUP_BEGIN;	
		/// Vstupny datovy stlpec. 
		/// INPUT_COLUMN == OUTPUT_COLUMN pretoze zdielaju data.
		static const columnType INPUT_COLUMN;
		/// Vystupny datovy stlpec.
		/// INPUT_COLUMN == OUTPUT_COLUMN pretoze zdielaju data.
		static const columnType OUTPUT_COLUMN;
		/// Vstupny argument z modelu.
		static const inType IN_ARC;
		/// Pociatocny vystupny argument pre vetvenie zadany v modele.
		/// Ostatne vystupne argumenty su na poziciach s posunom 
		/// +1, +2 ... ,+{@link forkFactor_} -1
		static const outType OUT_ARC_BEGIN;
		/// Meno parametru box, ktory urcuje vetvenie {@link forkFactor_}.
		/// Tj. vetvenie je konfigurovane skrz hodnotu parameteru boxu v modele.
		static const char* FORK_FACTOR_PARAM_NAME;
		static const char* ALL_DATA_PARAM_NAME;
		ForkBox(const BoxParamPack& paramsPack) 
			: RootBox(paramsPack), 
			forkFactor_(1) {

				forkFactor_ = 
					getBoxParameterValue(paramsPack, FORK_FACTOR_PARAM_NAME, 1);

		}


		virtual void initImpl() override {
			
			add_group(INPUT_GROUP, INPUT_COLUMN, get_input_descriptors(IN_ARC));

			for (int i = 0; i < forkFactor_; ++i) {
				add_group(getOutGroup(i), OUTPUT_COLUMN, 
					get_output_descriptors(getOutArc(i)));
			}
			wait4Receive();
		}


		virtual void syncMachEtwas() override {				

			GroupPtr ig = getGroupPtr(INPUT_GROUP);
			
			int dataSize = ig->getReadEnd();
			const int chunkSize = dataSize / forkFactor_; 
			int residue = dataSize % forkFactor_;
			
			LOG_T("Pocet delenych dat = " << dataSize 
				<< ", vyslednych vetiev = " << forkFactor_ 
				<< ", bezna velkost useku = " << chunkSize 
				<< ", zvysok dat = " << residue)

			assert(residue < forkFactor_);
			// prvy posun od nuly
			int residueOffset = startBranch(0, 0, chunkSize, residue);
			// start novy vetiev okrem poslednej 			
			for (int i = 1; i < forkFactor_; ++i) {				
				// ostatne posuny sa robia relativne takze stale o rovnaku cast
				// +- residue 
				residueOffset = startBranch(i, chunkSize + residueOffset, 
					2*chunkSize + residueOffset, residue);
			}

			sendSignalsOut();
			wait4Receive();
		}

	protected:
		/// Transformacia logickych indexov argumentov vystupu na fyzicke.
		static outType getOutArc(int index);

		/// Transformacia logickych indexov vystupnych skupin na fyzicke.
		static groupType getOutGroup(int index);

		/**
		 * Spustenie vetvy vypoctu nad datami v stanovenom rozsahu.
		 * 
		 * Metoda rozvetvi vypocet do boxu, ktory je spojeny s tymto boxom 
		 * v poradi urcenom parametrom index. Datam je nastaveny rozsah podla 
		 * parametrov from a to. Posun je vykonany relativne a preto je potrebne 
		 * uvazovat aj predosle posuny. Velkost posunu moze byt korigovana o 1 
		 * podla parametra residue pokial pocet dat nie je delitelny poctom 
		 * vyslednych vetiev.
		 * 
		 * @param index je logicky index vystupnej skupiny kde sa 
		 * posiela vypocet sucasne aj logicky index vystupnych argumentov 
		 * z modelu (z def. boxu su si rovne)
		 * @param from pociatok odosielanych dat 
		 * @param to koniec odosielanych dat 
		 * @param residue pocet revysujucich data, ktore treba tiez odoslat 
		 * (pokial su prevysujuce data tak sa postupne ku vetvam pridava 
		 * 1 "dato")
		 **/
		int startBranch(int index,	const int from, const int to, int& residue) {
			assert(residue < forkFactor_);
			int res = (residue-- > 0 ? 1 : 0);
			GroupPtr og = getGroupPtr(getOutGroup(index));				
			og->relocate(from, to + res);
			send(getOutArc(index), getOutGroup(index));
			return res;
		}


		/// Vyckavanie na prichod vsetkych vstupnych signalov + dat 
		/// na datovy vstup.
		void wait4Receive() {
			wait4SignalsIn();
			receive(IN_ARC, INPUT_GROUP);
		}

	private:
		/// Pocet vetiev do ktorych za rozdeli vypocet.
		int forkFactor_;
	};
} //    namespace jm4bbx
#endif
